package cn.example.util.toolkit;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * MD5加解密
 * Created by WuYB on 2017/4/19.
 */
public class EncryptUtil {

    private static final Logger log = LoggerFactory.getLogger(EncryptUtil.class);
    private static final String MD5 = "MD5";
    private static final String SHA1 = "SHA-1";
    private static final String SHA256 = "SHA-256";
    private static final String CHARSET = "UTF-8";

    private static class SingletonHolder {
        private static final EncryptUtil INSTANCE = new EncryptUtil();
    }

    private EncryptUtil() {
    }

    public static EncryptUtil getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * MD5加密（大写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpUpMD5(String source) {
        String str = signature(source, MD5);
        return str == null ? null : str.toUpperCase();
    }

    /**
     * MD5加密（小写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpLowMD5(String source) {
        String str = signature(source, MD5);
        return str == null ? null : str.toLowerCase();
    }

    /**
     * SHA1加密（大写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpUpSHA1(String source) {
        String str = signature(source, SHA1);
        return str == null ? null : str.toUpperCase();
    }

    /**
     * SHA1加密（小写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpLowSHA1(String source) {
        String str = signature(source, SHA1);
        return str == null ? null : str.toLowerCase();
    }

    /**
     * SHA256加密（大写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpUpSHA256(String source) {
        String str = signature(source, SHA256);
        return str == null ? null : str.toUpperCase();
    }

    /**
     * SHA256加密（小写）
     *
     * @param source 明文字符串
     * @return String
     */
    public String encpLowSHA256(String source) {
        String str = signature(source, SHA256);
        return str == null ? null : str.toLowerCase();
    }

    /**
     * Base64编码
     *
     * @param source 明文字符串
     * @return String
     */
    public String encodeBase64(String source) {
        try {
            byte[] bytes = encodeBase64(source.getBytes(CHARSET));
            return new String(bytes, CHARSET);
        } catch (UnsupportedEncodingException e) {
            log.error("Can not encode the string to Base64", e);
            return null;
        }
    }

    /**
     * Base64编码
     *
     * @param source 明文字节数组
     * @return String
     */
    public byte[] encodeBase64(byte[] source) {
        return Base64.getEncoder().encode(source);
    }

    /**
     * Base64解码
     *
     * @param source 密文字符串
     * @return String
     */
    public String decodeBase64(String source) {
        try {
            byte[] bytes = Base64.getDecoder().decode(source);
            return new String(bytes, CHARSET);
        } catch (UnsupportedEncodingException e) {
            log.error("Can not decode the string to Base64", e);
            return null;
        }
    }

    /**
     * Base64解码
     *
     * @param source 密文字节数组
     * @return String
     */
    public byte[] decodeBase64(byte[] source) {
        return Base64.getDecoder().decode(source);
    }

    /**
     * RSA公钥加密（UTF-8）
     *
     * @param publicKey 公钥字符串
     * @param source    明文字符串
     * @return String
     */
    public String encryptRSA(String publicKey, String source) {
        try {
            PublicKey key = getPublicKey(publicKey);
            byte[] resultBytes = encryptRSA(key, source.getBytes(CHARSET));
            return new String(Base64.getEncoder().encode(resultBytes), CHARSET);
        } catch (GeneralSecurityException | UnsupportedEncodingException e) {
            log.error("Can not signature the string with RSA", e);
            return null;
        }
    }

    /**
     * RSA私钥解密（UTF-8）
     *
     * @param privateKey 私钥字符串
     * @param source     密文字符串
     * @return String
     */
    public String decryptRSA(String privateKey, String source) {
        try {
            PrivateKey key = getPrivateKey(privateKey);
            byte[] resultBytes = decryptRSA(key, Base64.getDecoder().decode(source.getBytes(CHARSET)));
            return new String(resultBytes, CHARSET);
        } catch (GeneralSecurityException | UnsupportedEncodingException e) {
            log.error("Can not signature the string with RSA", e);
            return null;
        }
    }

    /**
     * 对字符串签名,签名算法可使用MD5,SHA-1,SHA-256
     *
     * @param source         要加密的字符串
     * @param encryptionType 加密类型
     * @return String
     */
    private String signature(String source, String encryptionType) {
        try {
            byte[] bt = source.getBytes(CHARSET);
            MessageDigest md = MessageDigest.getInstance(encryptionType);
            md.update(bt);
            return bytes2Hex(md.digest());
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            log.error("Can not signature the string to " + encryptionType, e);
            return null;
        }
    }

    /**
     * 字节转换
     *
     * @param bts 需要转换的字节
     * @return 转换后的字符串
     */
    private String bytes2Hex(byte[] bts) {
        StringBuilder des = new StringBuilder();
        String tmp = null;
        for (byte bt : bts) {
            tmp = (Integer.toHexString(bt & 0xFF));
            if (tmp.length() == 1) {
                des.append("0");
            }
            des.append(tmp);
        }
        return des.toString();
    }

    /**
     * RSA公钥加密
     *
     * @param publicKey RSA公钥
     * @param srcBytes  待加密byte数组
     * @return byte[]
     */
    private byte[] encryptRSA(PublicKey publicKey, byte[] srcBytes) throws GeneralSecurityException {
        //Cipher负责完成加密或解密工作，基于RSA
        Cipher cipher = Cipher.getInstance("RSA");
        //根据公钥，对Cipher对象进行初始化
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(srcBytes);
    }

    /**
     * RSA私钥解密
     *
     * @param privateKey RSA私钥
     * @param srcBytes   待解密byte数组
     * @return byte[]
     */
    private byte[] decryptRSA(PrivateKey privateKey, byte[] srcBytes) throws GeneralSecurityException {
        //Cipher负责完成加密或解密工作，基于RSA
        Cipher cipher = Cipher.getInstance("RSA");
        //根据公钥，对Cipher对象进行初始化
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(srcBytes);
    }

    /**
     * 转换RSA公钥
     *
     * @param publicKey RSA公钥
     * @return PublicKey
     */
    private PublicKey getPublicKey(String publicKey) throws GeneralSecurityException {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 转换RSA私钥
     *
     * @param privateKey RSA私钥
     * @return PrivateKey
     */
    private PrivateKey getPrivateKey(String privateKey) throws GeneralSecurityException {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

}
